#pragma once
#include "../build/release-install-cpp11/include/muduo/net/TcpServer.h"
#include "../build/release-install-cpp11/include/muduo/net/EventLoop.h"
#include "../build/release-install-cpp11/include/muduo/net/TcpConnection.h"
#include "../build/release-install-cpp11/include/muduo/net/Buffer.h"
#include "../build/release-install-cpp11/include/muduo/net/EventLoopThread.h"
#include "../build/release-install-cpp11/include/muduo/net/TcpClient.h"
#include "AbstractType.hpp"
#include "Unit.hpp"
#include "Message.hpp"
#include <unordered_map>
#include <mutex>
namespace Net
{

    // 基于muduo库的buffer对象
    class buffer : public Abstract::BaseBuffer
    {
    public:
        buffer(muduo::net::Buffer *buf) : _buf(buf)
        {
        }
        // 获取缓冲区的字节数
        size_t ReadAllSize() override
        {
            return _buf->readableBytes();
        }
        // 尝试从缓冲区读取4字节，但是缓冲区不删除读取的数据
        // 读取的四个字节是网络序列的--大端存储，要转换为本地存储序列
        int32_t Peek_4Byte() override
        {
            return _buf->peekInt32();
        }
        // 删除缓冲区4字节数据
        void Delete_4Byte() override
        {
            _buf->retrieveInt32();
        }
        // 读取4字节并且从缓冲区删除
        int32_t Read_4Byte() override
        {
            return _buf->readInt32(); // 从char类型的缓冲区读取4个字节并且拼接转成当地字节序的int类型
        }
        // 从缓冲区读取指定字节的数据以字符串形式返回
        std::string GetAsString(size_t len) override
        {
            return _buf->retrieveAsString(len);
        }

    private:
        // 这个buf对象不能让我们去管理它的销毁
        // Buffer这个对象是mudou库定义的什么时候销毁也是mudou库决定的
        // 不能用指针指针去管理 muduo::net::Buffer对象的指针
        muduo::net::Buffer *_buf;
    };

    // 定义 工厂类去提供构造方法
    class BuildBuffer
    {
    public:
        static std::shared_ptr<Abstract::BaseBuffer> build(muduo::net::Buffer *buf)
        {
            return std::make_shared<buffer>(buf);
        }
    };

    // 自定义协议处理对象-解析自定义报文和构建自定义报文
    class LVProtocol : public Abstract::BaseProtocol
    {
    public:
        // 能否解析该buffer中接收的消息
        // 一条完整的消息的组成:4个字节表示后续消息的字节数，4个字节表示请求类型的数值，4个字节表示
        // uuid的长度，最后就是正文部分
        bool IsDeal(const Abstract::BaseBuffer::BuffPtr &buf) override
        {
            if (buf->ReadAllSize() < 4)
                return false;
            // 从尝试读取表示后续消息长度的前四个字节拼接转成int类型
            int32_t total_len = buf->Peek_4Byte();
            // total_len=htons(total_len);//转主机序列
            //  如果当前缓冲区的字节数小于后续消息长度加上前面4个字节的长度
            //  那么缓冲区的消息是不够一条完整的消息的
            //DLOG("%d %d", total_len, buf->ReadAllSize());
            if (buf->ReadAllSize() < (total_len + 4))
                return false;

            return true;
        }
        // 把接收的消息中的各个元素解析到message对象中去
        bool Analizy_Message(const Abstract::BaseBuffer::BuffPtr &buf, Abstract::BaseMessage::MesaPtr &msg) override
        {
            // 读取并且清空缓冲区的内容
            int32_t total_len = buf->Read_4Byte(); // 读取表示后续消息长度的4字节
            //DLOG("totalen:%d", total_len);
            Rs_RqType type = (Rs_RqType)buf->Read_4Byte(); // 接着读取表示请求类型的4个字节
            int32_t idlen = buf->Read_4Byte();             // 接着读取表示uuid长度的四个字节
            // 正文的字节数等于后续消息的字节数减去表示请求类型的4个字节和表示uuid长度的四个字节
            // 和uuid本身的长度
            int32_t body_len = total_len - 4 - 4 - idlen;
            // 从当前位置接着读取指定长度的uuid字符串，缓冲区清空读取的数据
            std::string uuid = buf->GetAsString(idlen);
            std::string body = buf->GetAsString(body_len);

            // 创建子类对象用父类智能指针接收，由外部传入的基类的智能指针对象引用带出去
            // 虽然抽象基类不能实例化对象但是可以创建智能指针对象和基类指针变量和引用
            // 本质都是维护一个基类类型的指针变量，并没有实例化基类对象，这个指针变量可以用来接收
            // 派生类对象的地址或者智能指针对象
            msg = Message::BuildMessage::create(type);
            if (msg.get() == nullptr)
            {
                DLOG("消息对象类型错误，创建消息对象失败");
                return false;
            }
            //DLOG("接收的消息正文:\n%s", body.c_str());
            bool ret = msg->UnSerialize(body);
            if (ret == false)
            {
                DLOG("消息正文反序列化失败");
                return ret;
            }
            msg->SetUuid(uuid);
            msg->SetMtype(type);
            return true;
        }
        // 把message对象中消息的body正文字段加上报头组成一条完整的消息；
        std::string Serialize(Abstract::BaseMessage::MesaPtr msg) override
        {
            // 获取序列化后的正文字段
            std::string body = msg->Serialize();

            // 对于一个int类型的变量来说是有必要同一字节序的，因为不同的主机存储一个变量的大小端不同
            // 如果不转字节序，就会导致一个小端存储的int变量从低地址开始把四个字节发送给对方，对方
            // 一次拿取四个字节按照低地址到高地址依次存放这个四个字节，如果对方是大端存储就会导致本来低地址
            // 的一个字节是数据的高位变成了是数据的最低位字节了。
            uint32_t type = htonl((uint32_t)msg->GetMtype()); // 主机存储序列转网络序列(大端存储)
            std::string uuid = msg->GetUuid();
            int32_t idlen = uuid.size();
            int32_t total_len1 = htonl(body.size() + 4 + 4 + idlen);
            int32_t total_len2 = body.size() + 4 + 4 + idlen;
            idlen = htonl(uuid.size());
            std::string Mesg;
            Mesg.reserve(total_len2); // 提前开好空间
            // 把一个表示长度的整数的每一个字节当做char类型添加到消息中去
            // 接收端再拿出四个char类型的字节拼接在一起转成整形即可获得表示消息长度值
            Mesg.append((char *)&total_len1, 4);
            Mesg.append((char *)&type, 4);
            Mesg.append((char *)&idlen, 4);
            Mesg.append(uuid);
            Mesg.append(body);

            DLOG("%s", Mesg.c_str());
            return Mesg;
        }
    };
    class BulidLVProtocol
    {
    public:
        static std::shared_ptr<Abstract::BaseProtocol> build()
        {
            return std::make_shared<LVProtocol>();
        }
    };

    // 网络链接管理模块负责管理客户端连接和发送消息给客户端
    class MuduoConnection : public Abstract::BaseConnection
    {
    public:
        MuduoConnection(const muduo::net::TcpConnectionPtr &conn,
                        std::shared_ptr<Abstract::BaseProtocol> &lvpro) : _conn(conn), _proc(lvpro)
        {
        }
        // 负责发送有message对象组指的正文消息加自定义协议层对象添加的报头
        void Send(Abstract::BaseMessage::MesaPtr msg) override
        {
            std::unique_lock<std::mutex> lock(_mutex); // 客户端多线程发送请求加锁保护
            DLOG("发送一条消息:");
            std::string str = _proc->Serialize(msg); // 给msg对象中的body正文添加报头
            //std::cout << str << "长度:" << str.size() << std::endl;
            _conn->send(str);
        }
        // 关闭连接
        void ShutDown() override
        {
            _conn->shutdown();
        }
        // 查询链接状态
        bool IsConnect() override
        {
            return _conn->connected();
        }

    private:
        std::mutex _mutex; // 锁保护--客户端可能有多个线程同时发送请求
        // muduo::net::TcpConnectionPtr一个客户端的连接对象--封装了fd网络文件描述符
        muduo::net::TcpConnectionPtr _conn;
        // 自定义协议层对象帮助链接对象处理和发送一条完整的消息
        std::shared_ptr<Abstract::BaseProtocol> _proc;
    };

    class BulidMuduoConnection
    {
    public:
        static std::shared_ptr<Abstract::BaseConnection> build(const muduo::net::TcpConnectionPtr &conn,
                                                               std::shared_ptr<Abstract::BaseProtocol> &lvpro)
        {
            return std::make_shared<MuduoConnection>(conn, lvpro);
        }
    };

    // 负责创建网络监听套接字和接收客户端连接并且给客户端分配
    // epoll对象去死循环监听客户端的fd
    class MuduoServer : public Abstract::BaseServer
    {
    public:
        MuduoServer(uint16_t port)
            : _server(&_baseloop, muduo::net::InetAddress("0.0.0.0", port), "DictServer", muduo::net::TcpServer::kReusePort), _proc(BulidLVProtocol::build())
        {
        }

        void start() override
        {
            // 设置网络监听文件的就绪时的回调函数处理--主线程epoll
            // 模块中会为新的连接fd创建一个TcpConnectionPtr来管理,链接断开时也会调用该函数
            _server.setConnectionCallback(
                [this](const muduo::net::TcpConnectionPtr &conn) -> void
                {
                    OnConnection(conn);
                });
            // 客户端fd的就绪事件的回调函数处理
            _server.setMessageCallback(
                [this](const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp T)
                {
                    DealMessage(conn, buf, T);
                });
            _server.start();  // 开始启动服务端--获取网络监听文件，绑定ip和port
            _baseloop.loop(); // 当前线程死循环监听所有的fd的就绪状态
        }

    private:
        // muduo::net::TcpConnectionPtr一个客户端的连接对象--封装了fd网络文件描述符
        // 客户端成功连接时的epoll线程的回调函数创建一个TcpConnectionPtr对象并且传入该回调函数
        void OnConnection(const muduo::net::TcpConnectionPtr &conn)
        {
            // 客户端连接成功
            if (conn->connected())
            {
                DLOG("链接建立成功");
                Abstract::BaseConnection::ConPtr muduo_con = Net::BulidMuduoConnection::build(conn, _proc);
                _conns.insert({conn, muduo_con}); // 创建关联关系
                // 调用自己的处理链接成功的回调函数
                if (_ConCback)
                    _ConCback(muduo_con);
            }
            else // 如果链接断开
            {
                if (_conns.count(conn))
                {
                    auto it = _conns.find(conn);
                    Abstract::BaseConnection::ConPtr muduo_con = it->second;
                    _conns.erase(it); // 清除mudou库的连接对象和自定义的连接对象的关系
                    if (_CloCBack)
                        _CloCBack(muduo_con); // 调用自定义链接对象的回调函数
                }
            }
        }

        // 监听的客户端的连接fd读事件就绪处理的回调函数
        // 每个TcpConnectionPtr连接对象都有自己独立的缓冲区去存储从
        // 内核读取上来的消息。读取多少就清空多少
        void DealMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp T)
        {
            DLOG("接收到消息，进入消息模块");
            auto Base_buf = BuildBuffer::build(buf); // 基于连接对象的缓冲区建立自己的缓冲区对象
            // 获取自己定义的链接对象
            if (_conns.count(conn) == 0)
            {
                DLOG("链接对象不存在");
                return;
            }
            Abstract::BaseConnection::ConPtr muduo_con = _conns[conn];
            // 处理信息
            while (true)
            {
                // 当前客户端缓冲区不够一条消息的处理等待下一次客户端读事件就绪处理
                if (_proc->IsDeal(Base_buf) == false)
                {
                    DLOG("消息不够一条");
                    break;
                }

                // 处理buffer中完整的消息，把消息各个元素解析并且根据请求类型
                // 创建不同的message处理对象用基类message智能指针管理
                Abstract::BaseMessage::MesaPtr Msgptr;
                bool ret = _proc->Analizy_Message(Base_buf, Msgptr);
                if (ret == false)
                {
                    DLOG("客户端消息解析失败"); // 直接关闭连接
                    conn->shutdown();
                    _conns.erase(conn); // 清除mudou库的连接对象和自定义的连接对象的关系
                    if (_CloCBack)
                        _CloCBack(muduo_con); // 调用自定义关闭链接对象的回调函数
                    return;
                }
                // 客户端消息解析成功调用对应的回调函数存储对应的客户端连接对象和消息对象
                if (_ReaCBack)
                    _ReaCBack(muduo_con, Msgptr); // 调用自定义关闭链接对象的回调函数
            }
        }

    private:
        // 协议层对象用来构造连接关联对象
        std::shared_ptr<Abstract::BaseProtocol> _proc;

        // 把muduo库创建的管理客户端连接的对象和我们自己管理客户端连接的对象关联起来
        std::unordered_map<muduo::net::TcpConnectionPtr, Abstract::BaseConnection::ConPtr> _conns;
        // 创建一个epoll模型来监听fds的就绪状态
        // 每次新的连接到来epoll模型都会创建一个TcpConnectionPtr对象
        // 来管理这个客户端连接就是封装了客户端的符到
        muduo::net::EventLoop _baseloop;
        // 定义服务器对象--申请一个网络监听套接字并且绑定端口号和ip
        muduo::net::TcpServer _server;
    };

    class BulidMuduoServer
    {
    public:
        static std::shared_ptr<Abstract::BaseServer> bulid(uint16_t port)
        {
            return std::make_shared<MuduoServer>(port);
        }
    };

    // 客户端的网络文件连接不需要绑定ip和端口，os会自动绑定
    class MuduoClient : public Abstract::BaseClient
    {
    public:
        // 给muduo::net::TcpClient _client初始化完成时创建了一个epoll线程去监听客户端的fd(TcpConnectionPtr)对象了
        // 但是还没有连接服务器成功时是会阻塞的，连接成功先把连接对象推送给eventloop线程去监听
        // 然后主线程再执行连接成功的回调函数
        MuduoClient(const std::string &sip, uint16_t sport)
            : _client(_loop, muduo::net::InetAddress(sip, sport), "MuduoClient"), _CDL(1), _loop(_threadloop.startLoop()), _proc(Net::BulidLVProtocol::build())
        // 判断链接状态
        {
        }
        bool IsConnect() override
        {
            return _Con->IsConnect();
        }
        // 链接服务器
        void ConnectServer() override
        {
            // 设置连接成功时的回调函数--当客户端连接服务端成功时执行该回调函数--构建一个
            // /TcpConnectionPtr对象传递给回调函数负责IO
            _client.setConnectionCallback([this](const muduo::net::TcpConnectionPtr &conn) -> void
                                          { OnConnection(conn); });
            // 设置客户端fd的就绪事件的回调函数--当客户端fd接收到数据时执行该回调函数
            _client.setMessageCallback([this](const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp T)
                                       { DealMessage(conn, buf, T); });

            // 连接服务端--这个函数不是阻塞的可能没有连接成功就去send数据了
            // 连接成功创建一个TcpConnectionPtr连接对象封装了客户端的fd推送给epoll线程去监听
            // 同时回调连接成功时的回调函数
            //DLOG("连接中");
            _client.connect();
            //DLOG("连接中");
            // 程序执行到这里时如果计算器的值没有为0，那么会被加入到条件变量等待唤醒
            _CDL.wait();
           // sleep(1);
           // DLOG("连接成功");
        }
        // 关闭连接
        void Shutdown() override
        {
            _Con->ShutDown(); // 连接关闭--客户端关闭网络文件fd
        }
        // 发送数据
        void Send(Abstract::BaseMessage::MesaPtr Mptr) override
        {
            if (_Con->IsConnect() == false)
            {
                DLOG("服务端连接断开");
                return;
            }
            // DLOG("发送一条消息");
            // std::cout<<Mptr->Serialize()<<std::endl;
            _Con->Send(Mptr);
        }
        // 获取链接对象-负责io处理的对象
        Abstract::BaseConnection::ConPtr GetConnection() override
        {
            //DLOG("获取一个链接对象");
            if (_Con.get() == nullptr)
            {
                DLOG("获取发送模块失败");
            }
            else
            {
                DLOG("获取发送模块成功");
            }
            return _Con;
        }

    private:
        // 连接建立成功时执行，epoll线程调用该函数给客户端fd创建一个TcpConnectionPtr对象
        void OnConnection(const muduo::net::TcpConnectionPtr &conn)
        {
            // 判断当前与服务端的连接状态即连接对象有没有注册到一个epoll线程中去监听
            DLOG("进入链接模块");
            if (conn->connected())
            {
                DLOG("进入链接模块");
                //std::cout << "client connect to server" << std::endl;
                // 防止客户端主线程刚把创建的连接对象推送给eventloop线程监听
                // 然后就有读事件就绪了调用回调函数时还没有创建自定义的连接对象
                // 因为是先推送连接对象给监听线程的
                if (_Con.get() == nullptr)
                {
                    _Con = Net::BulidMuduoConnection::build(conn, _proc); // 客户端连接成功绑定到自己的Connection对象中
                }

                if (_ConCback)
                    _ConCback(_Con);
                //一定要放到最后再减到0，负责监听线程可能还没有创建_Con 对象
                //主线程就去使用_Con 对象获取链接对象了造成段错误，先让主线程等待创建成功再往下走
                 _CDL.countDown(); // 确定连接成功，计数器减到0
            }
            else
            {
                _Con->ShutDown(); // 连接关闭--客户端关闭网络文件fd
                //std::cout << "server disconnect" << std::endl;
            }
        }
        // 客户端fd读事件就绪的回调函数
        void DealMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp T)
        {
            //ELOG("进入消息处理回调函数");
            auto Base_buf = Net::BuildBuffer::build(buf); // 基于连接对象的缓冲区建立自己的缓冲区对象
                                                          // 防止客户端主线程刚把创建的连接对象推送给eventloop线程监听
                                                          // 然后就有读事件就绪了调用回调函数时还没有创建自定义的连接对象
            if (_Con.get() == nullptr)
            {
                _Con = Net::BulidMuduoConnection::build(conn, _proc); // 客户端连接成功绑定到自己的Connection对象中
            }

            // 处理信息
            while (true)
            {
                // 当前客户端缓冲区不够一条消息的处理等待下一次客户端读事件就绪处理
                if (_proc->IsDeal(Base_buf) == false)
                    break;

                // 处理buffer中完整的消息，把消息各个元素解析并且根据请求类型
                // 创建不同的message处理对象用基类message智能指针管理
                Abstract::BaseMessage::MesaPtr Msgptr;
                bool ret = _proc->Analizy_Message(Base_buf, Msgptr);
                if (ret == false)
                {
                    ELOG("客户端消息解析失败"); // 直接关闭连接
                    conn->shutdown();
                    if (_CloCBack)
                        _CloCBack(_Con); // 调用自定义关闭链接对象的回调函数
                    return;
                }
                // 服务端消息解析成功调用对应的回调函数存储对应的服务端连接对象和消息对象
                if (_ReaCBack)
                    _ReaCBack(_Con, Msgptr); // 调用自定义关闭链接对象的回调函数
            }
        }

    private:
        // /TcpConnectionPtr负责网络套接字的IO处理
        // 协议层对象用来构造连接关联对象
        std::shared_ptr<Abstract::BaseProtocol> _proc;
        Abstract::BaseConnection::ConPtr _Con;
        muduo::net::EventLoopThread _threadloop;
        // 定义一个计数器--里面有加锁和条件变量
        muduo::CountDownLatch _CDL;
        muduo::net::EventLoop *_loop;
        // TcpClient对象只负责创建套接字和建立连接
        // 然后返回一个TcpConnectionPtr对象
        muduo::net::TcpClient _client;
    };

    class BulidMuduoClient
    {
    public:
        static std::shared_ptr<Abstract::BaseClient> build(const std::string &sip, uint16_t sport)
        {
            return std::make_shared<MuduoClient>(sip, sport);
        }
    };
}

/*
客户端工作流程：
a) 创建TcpClient对象：
指定EventLoop(事件循环)和服务器地址
内部创建TcpConnection和Connector
b) 连接建立过程：

Connector发起连接(Socket::connect)
连接成功后创建TcpConnection对象
注册到EventLoop进行事件监听然后执行连接成功的回调函数
c) 数据收发机制：

通过TcpConnection的send()方法发送数据
数据先写入输出缓冲区
当socket可写时从缓冲区发送
收到数据时触发messageCallback
核心组件：
EventLoop：事件循环，处理I/O事件
TcpClient：客户端入口类
Connector：负责主动连接
TcpConnection：管理连接状态和数据传输
Buffer：应用层缓冲区


*/